#include "ConvolutionNet.h"

vector <int> vectordouble_to_vectorint (vector <double> dubl)
{
    vector <int> result;
    for (int i=0; i<dubl.size(); i++)
    {
        able per;
        per.b=dubl[i];
        for (int k=0 ; k<8 ;k++)
        {
            for (int j=0 ; j<8 ;j++)
            {
                result.push_back(AT_BIT(per.a[k], j));
             };
        }
    };
    return result;
}


nucleus::nucleus(){}

nucleus::nucleus(int ht) /*Конструктор*/
{
    hight = ht;
    size = ht * ht;
    double init = (rand()%2000)/1000-1;
    for (int i=0 ; i < size; i++)
        w.push_back(init);
}

nucleus::nucleus(int ht, double* ww) /*Конструктор с известными весами*/
{
    hight = ht;
    size = ht * ht;
    w.clear();
    for (int i=0 ; i < size; i++)
        w.push_back(ww[i]);
}

nucleus::~nucleus()
{
}

void nucleus::set_nucleaus(double* wz)  /*Процедура устанавливает веса*/
{
    w.clear();
    for(int i =0; i < size; i++)w.push_back(wz[i]);
}

vector<double> nucleus::get_nucleaus() {return w;} /*Функция возвращает веса*/

void nucleus::set_hight(int ht) /*Устанавливает высоту ядра*/
{
    hight = ht;
    size = ht * hight;
}

int nucleus::get_hight() {return hight;}/*Получает высоту ядра*/

int nucleus::get_size() {return size;}/* Получает размер ядра*/

double nucleus::barrier_function(double _out) { return 1/(1+exp(-1*_out)); } /*Барьерная функция ТАМ В ЗАВИСИМОСТЬ ОТ РЕАЛИЗАЦИИ НАДО ИЗМЕНИТЬ*/

double nucleus::calculation(vector<double> pImage) //Вычисление для фрагмента image, какого либо изображения
{
    double sum = 0;
    for (int i = 0; i < size; i++) //Прогоняем по всем точкам вычисляем сумму, оператор + надо будет переопределить для класса
    {
        sum += w[i] * pImage[i];
        i++;
    }
    double result = barrier_function(sum);

    return result;
}

double nucleus::compresion(vector<double> pImage) //Вычисление для компрессии
{
    double sum = 0;
    for (int i = 0; i < size; i++) //Прогоняем по всем точкам вычисляем сумму, оператор + надо будет переопределить для класса
    {
        sum += w[i] * pImage[i];
        i++;
    }
    double result = sum;

    return result;
}

void nucleus::random_correct(int e)
{
    for (int i=0; i < size; i++)
    {
      w[i]=(rand()%2000)/1000-1;
        //w[i]=w[i] + delta;
    }
}



matrix::matrix(int ht, int dt) /*Конструктор пустого*/
{
    hight = ht;
    size = ht*ht;
    color_depth = dt;
    double init = 0;
    p.clear();
    for (int i = 0 ; i < size; i++)
        p.push_back(init);
}

matrix::matrix(int ht, int dt, double* pt) /*Конструктор со знанием точек*/
{
    hight = ht;
    size = ht * ht;
    color_depth = dt;
    p.clear();
    for (int i = 0 ; i < size; i++)
        p.push_back(pt[i]);
}

matrix::~matrix(){}

void matrix::set_depth(int dt) /*Устанавливаем глубину */
{
    color_depth = dt;
}

void matrix::set_hight(int ht) /*Устанавливаем высоту*/
{
    hight = ht;
    size = ht * hight;
}

int matrix::get_depth() {return color_depth;}
int matrix::get_hight() {return hight;}
int matrix::get_size() {return size;}

void matrix::set_matrix(double* pz) // устанавливает веса
{
    p.clear();
    for(int i = 0; i < size; i++)
        p.push_back(pz[i]);
}
vector<double> matrix::get_matrix() {return p;} // получает веса

matrix matrix::get_fragment(int ht, int _x, int _y) //Возвращает матрицу фрагмента изображения шириной wt и высотой ht начиная с точки с номером х (левый верхний угол)
{
    /*Вычисляем координаты точки*/
    matrix init(ht, color_depth); //Создаем меньшую матрицу
    double* per = new double[ht*ht];
    for (int i = _y; i < (_y + ht); i++) //Добавлем соответствующие точки
        for (int j = _x; j < (_x + ht); j++)
        {
            per[(i-_y) * ht + (j-_x)] = p[i * hight + j];
        }
    init.set_matrix(per);
    delete[] per;

    return init;
}



single_sigmoid::single_sigmoid(int kol,int kol_image)
{
   count=kol;
   count_image=kol_image;
   w0=(rand()%100)/100-0.5;
   w.clear();
   for (int i=0; i<count; i++)
   {
       w.push_back((rand()%100)/100-0.5);
   }
}

double single_sigmoid::barrier_function(double _out) {if (_out>0) return 1; else return 0;} //ИЗМЕНИЛЛЛЛЛЛЛЛЛЛЛ

int single_sigmoid::calculation(vector <double> scalar)
{
    vector <int> scalar_int;
    scalar_int= vectordouble_to_vectorint(scalar);
    double sum=w0;
    for(int i = 0; i < count; i++)
    {
        sum+=scalar_int[i]*w[i];
    }
    double result = barrier_function(sum);
    return  result;
}

int single_sigmoid::once_correct(pair<vector <double>, int>  training_set, int era,int nomer)
{
    if ((training_set.second==nomer) && calculation(training_set.first) || (training_set.second!=nomer) && !calculation(training_set.first)) return 0;
    else
    {
        if (calculation(training_set.first))
        {
            vector <int> scalar_int;
            scalar_int= vectordouble_to_vectorint(training_set.first);
            for(int i = 0; i < count; i++)
            {
                w[i]-=scalar_int[i]*1/(1+era/3);
            }
        }
        else
        {
            vector <int> scalar_int;
            scalar_int= vectordouble_to_vectorint(training_set.first);
            for(int i = 0; i < count; i++)
            {
                w[i]+=scalar_int[i]*1/(1+era/3);
            }
        }
    }
    return 1;

}

net_sigmoid::net_sigmoid(int count1, int kol,int kol_image)
{
    single_sigmoid *sigmoid;
    count=count1;
    for (int i=0; i<count1; i++)
    {
        sigmoid = new single_sigmoid(kol, kol_image);
        w.push_back(*sigmoid);
        //Освободить память НУЖНО
    }
}

int net_sigmoid::learning_one_era (vector <pair<vector <double>, int> >  training_set, int era)
{
    int error_count=0;
    for (int i=0; i<count; i++)
        for (int j=0; j<training_set.size(); j++)
        {
            error_count+=w[i].once_correct(training_set[j],era,i);
        }
    qDebug() << "era - "<<era<<" error_count - "<<error_count;
    return error_count;

}

void net_sigmoid::learning (vector <pair<vector <double>, int> >  training_set)
{
    int era=0;
    while ((learning_one_era (training_set, era)>0) && (era<100))
    {
        era++;
    }
}

vector <int> net_sigmoid::calculation_list (vector <double>  training_set)
{
    vector <int> result;
    for (int i=0; i<count; i++)
    {
        result.push_back(w[i].calculation(training_set));

    }
    return result;
}



layer_two::layer_two() {}

layer_two::layer_two(int count, vector <nucleus> n, nucleus comp)
{
    count_of_nucleus=count;
    nuc=n;
    compression_ratio=comp;
}

matrix layer_two::calculation_once(matrix _matrix, nucleus _nucleus) //Вычисление выходной матрицы, после работы сети с матрицей _matrix и ядром _nucleus
{
    int size =_nucleus.get_hight();
    int wt = _matrix.get_hight() - size + 1;
    matrix init(wt, _matrix.get_depth());
    /*
    Умножаем на ядро все возможные фрагменты, в соответствии с новым получением фрагмента
    */
    for (int i=0; i < (wt); i++)
        for (int j=0; j < (wt); j++)
        {
            matrix fragment = _matrix.get_fragment(_nucleus.get_hight(), j, i);
            init.p[i*wt + j] = _nucleus.calculation(fragment.get_matrix());
        }

    return init;
}

vector<matrix> layer_two::calculation (vector <matrix> mas_image) //свертывание с mas_image - вектор входных матриц
{
    vector <matrix> init;
    for (size_t j=0; j < mas_image.size(); j++)
        for (int i=0; i < count_of_nucleus; i++)
        {
            init.push_back(calculation_once(mas_image[j], nuc[i]));
        }

    return init;
}

matrix layer_two::compression_once(matrix _matrix) //Вычисление выходной матрицы, после сжатия матрицы _matrix
{
    matrix *init = new matrix(ceil(_matrix.get_hight()/compression_ratio.get_hight()),_matrix.get_depth());

    for (int i = 0; i < _matrix.get_hight(); i += compression_ratio.get_hight())
        for (int j = 0; j < _matrix.get_hight(); j += compression_ratio.get_hight())
        {
            matrix tmp = _matrix.get_fragment(compression_ratio.get_hight(), j, i);
            init->p[(i/compression_ratio.get_hight()) * init->get_hight()+(j/compression_ratio.get_hight())] = compression_ratio.compresion(tmp.get_matrix());
        }

    return *init;
}

vector<matrix> layer_two::compression (vector <matrix> mas_image) //сжатие mas_image - вектор входных матриц
{
    vector <matrix> init;
    for (size_t j = 0; j < mas_image.size(); j++)
        {
            init.push_back(compression_once(mas_image[j]));
        }
    return init;
}

void layer_two::random_correct(int e)
{
    for (size_t i = 0; i < nuc.size(); i++)
    {
        nuc[i].random_correct(e);
    }
}




convolution_net::convolution_net()
{
    e = 0;

    const int count_layers = 6; // число слоёв сети

    // размеры ядер по слоям
    int heiht_nucleus[count_layers] = { 5, 2, 5, 2, 4, 2};

    int count_nucleus[count_layers] = {4, 1, 3, 1, 2, 1};

    // вектор ядер
    vector<vector<nucleus> > nucleus_layers;

    for(int i = 0; i < count_layers; i++)
    {
        // заполняем веса
        int N = heiht_nucleus[i] * heiht_nucleus[i];
        double weights_layer[N];
        //Ядра компрессии заполняются не случайными

        if ((i==1) || (i==3) ||(i==5))
        {
            for(int j = 0; j < N; j++)
                weights_layer[j] = 0.25;
        }
        else
        {
            for(int j = 0; j < N; j++)
                weights_layer[j] = (rand()%1000/1000);
        }
        // заполняем слои ядрер
        vector<nucleus> tmp2;
        for(int j = 0; j < count_nucleus[i]; j++)
        {
            tmp2.push_back(nucleus(heiht_nucleus[i], weights_layer));
        }

        nucleus_layers.push_back(tmp2);
    }

    // создаём слои сети
    layer_12 = new layer_two(count_nucleus[0], nucleus_layers[0], *nucleus_layers[1].begin());
    layer_34 = new layer_two(count_nucleus[2], nucleus_layers[2], *nucleus_layers[3].begin());
    layer_56 = new layer_two(count_nucleus[4], nucleus_layers[4], *nucleus_layers[5].begin());
    //   ВНИМАНИЕ ИЗМЕНЕНИЕ!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    layer_7 = new net_sigmoid(10,24*64, 4);
}

vector <pair<vector <double>, int> > convolution_net::in_double(vector <pair<matrix, int> > training_set)
{
    vector <pair<vector <double>, int> > result;
    for (int i=0; i<training_set.size(); i++)
    {
        pair<vector <double>, int> per;
        vector <matrix> matr;
        matr.push_back(training_set[i].first);
        per.first=calculation_without_sigmoid(matr);
        per.second=training_set[i].second;
        result.push_back(per);
    }

    return result;
}

vector <int> convolution_net::calculation(vector <matrix> _matrix)
{
    vector <matrix> init, init2;
    init.clear();
    init2.clear();
    init=layer_12->calculation(_matrix);
    init2=layer_12->compression(init);
    init.clear();
    init=layer_34->calculation(init2);
    init2.clear();
    init2=layer_34->compression(init);
    init.clear();
    init=layer_56->calculation(init2);
    init2.clear();
    vector <double> result;
    for (size_t i=0; i < init.size(); i++)
    {
        for (int j=0; j < init[i].get_size(); j++)
            result.push_back(init[i].get_matrix()[j]);
    }
    return layer_7->calculation_list(result);
}

vector<double> convolution_net::calculation_without_sigmoid(vector <matrix> _matrix)
{
    vector <matrix> init, init2;
    init.clear();
    init2.clear();
    init=layer_12->calculation(_matrix);
    init2=layer_12->compression(init);
    init.clear();
    init=layer_34->calculation(init2);
    init2.clear();
    init2=layer_34->compression(init);
    init.clear();
    init=layer_56->calculation(init2);
    init2.clear();
    vector <double> result;
    for (size_t i=0; i < init.size(); i++)
    {
        for (int j=0; j < init[i].get_size(); j++)
            result.push_back(init[i].get_matrix()[j]);
    }
    return result;
}

int convolution_net::sum_error(vector <pair<matrix, int> > training_set )
{
   emit needToUpdateForm();
   int sum = 0;
   QThreadPool thPool;
   thPool.setMaxThreadCount(16);
   for (size_t i = 0; i < training_set.size(); i++)
   {
        MyRunnable* myR = new MyRunnable();
        myR->setWork(&(training_set[i]), &sum, this);
        thPool.start(myR);
   }
   thPool.waitForDone();
   return sum;
}

double convolution_net::sum_error_new(vector <pair<matrix, int> > training_set )
{
   //emit needToUpdateForm();
   double sum = 0.0;
   //QThreadPool thPool;
   for (size_t j=0; j<training_set.size();j++)
   {
       vector <double> matrix_j;
       vector <matrix> tr;
       tr.push_back(training_set[j].first);
       matrix_j=calculation_without_sigmoid(tr);
       tr.pop_back();
       for (size_t i = j+1; i < training_set.size(); i++)
       {
           tr.push_back(training_set[i].first);
           vector <double> matrix_i;
           matrix_i=calculation_without_sigmoid(tr);
           tr.pop_back();
           if (training_set[i].second==training_set[j].second)
           {
               for (size_t k=0; k<matrix_i.size(); k++)
               {
                   sum+=5*(matrix_i[k]-matrix_j[k])*(matrix_i[k]-matrix_j[k]);
               }
           }
           else
           {
               for (size_t k=0; k<matrix_i.size(); k++)
               {
                   sum-=(matrix_i[k]-matrix_j[k])*(matrix_i[k]-matrix_j[k]);
               }
           }
        //MyRunnable* myR = new MyRunnable();
        //myR->setWork(&(training_set[i]), &sum, this);
        //thPool.start(myR);
       }
   }
   //thPool.waitForDone();
   return sum;
}




void convolution_net::random_correct(int e)
{
   layer_12->random_correct(e);
   layer_34->random_correct(e);
   layer_56->random_correct(e);
}

void convolution_net::run_era_with_histograms(vector <pair<matrix, int> > set)
{
   Histogram_dialog hd;

   for (size_t i = 0; i < set.size(); i++)
   {
        vector <matrix> per;
        per.push_back(set[i].first);

        QString title_hist;
        title_hist.sprintf("Sample %d, target_answer = %d", i, set[i].second);

        hd.AddHistogram(calculation_without_sigmoid(per), title_hist);
   }
   hd.exec();
}

void convolution_net::learning(vector <pair<matrix, int> > training_set)
{
   qDebug() << "convolution_net: learning perseptron ...";
   layer_7->learning(in_double(training_set));
}
///////////////////////////////////////////////////////////////////////////
void convolution_net::learning_new(vector <pair<matrix, int> > training_set)
{
   int local_e = 0;

   qDebug() << "convolution_net: learning ...";
   const int count_layers = 6; // число слоёв сети

   // размеры ядер по слоям
   int heiht_nucleus[count_layers] = { 5, 2, 5, 2, 4, 1};

   int count_nucleus[count_layers] = {4, 1, 3, 1, 2, 1};

   // вектор ядер
   vector<vector<nucleus> > nucleus_layers;

   for(int i = 0; i < count_layers; i++)
   {
       // заполняем веса
       int N = heiht_nucleus[i] * heiht_nucleus[i];
       double weights_layer[N];

       for(int j = 0; j < N; j++)
           weights_layer[j] = (rand()%1000/1000);

       // заполняем слои ядрер
       vector<nucleus> tmp2;
       for(int j = 0; j < count_nucleus[i]; j++)
       {
           tmp2.push_back(nucleus(heiht_nucleus[i], weights_layer));
       }

       nucleus_layers.push_back(tmp2);
   }

   layer_two* layer_12_save = new layer_two(count_nucleus[0], nucleus_layers[0], *nucleus_layers[1].begin());
   layer_two* layer_34_save = new layer_two(count_nucleus[2], nucleus_layers[2], *nucleus_layers[3].begin());
   layer_two* layer_56_save = new layer_two(count_nucleus[4], nucleus_layers[4], *nucleus_layers[5].begin());
   //single_sigmoid* layer_7_save = new single_sigmoid(24, 4);

   *layer_12_save = *layer_12;
   *layer_34_save = *layer_34;
   *layer_56_save = *layer_56;
   //*layer_7_save = *layer_7;

   double sum_err = sum_error_new(training_set);
   while (( sum_err > -10000) && (local_e < 10))
   {
       qDebug() << "era = " << e << ", summary_error: " << sum_err;
       e++;
       local_e++;

       random_correct(e);
       double err = sum_error_new(training_set );

       if (err > sum_err)
       {
           // откат значений
           qDebug() << "backtracking (summary_error after random correct = " << err << ") ...\n";

           *layer_12 = *layer_12_save;
           *layer_34 = *layer_34_save;
           *layer_56 = *layer_56_save;
          // *layer_7 = *layer_7_save;
       }
       else
       {
           // если не откатываем, то сохраним суммарную ошибку для сохраняемого набора образцов (чтоб постоянно не пересчитывать)
           sum_err = err;

           // пересохраним значения
           *layer_12_save = *layer_12;
           *layer_34_save = *layer_34;
           *layer_56_save = *layer_56;
           //*layer_7_save = *layer_7;
       }
   }
   qDebug() << "convolution_net: learning is ok!" << endl;

   delete layer_12_save;
   delete layer_34_save;
   delete layer_56_save;
   //delete layer_7_save;
}
//////////////////////////////////////////////////////////////////////




void MyRunnable::setWork(pair<matrix, int>* ptr, int* p_to_save_result, convolution_net* p_owner)
{
    if((ptr && p_to_save_result) && p_owner)
    {
        myWork = ptr;
        result = p_to_save_result;
        owner = p_owner;
    }
}

void MyRunnable::run ()
{
    if((myWork && result) && owner)
    {
        vector <matrix> per;
        per.push_back(myWork->first);
        //if (myWork->second != owner->calculation(per))
           // (*result)++;
    }
}
